大部分不會把資料庫連線帳號、密碼等相關訊息寫在程式碼裡面,通常寫在一個檔案裡面,程式裡面用key來讀取value,ASP.NET Core裡面是寫在appsettings.json,透過Configuration存取appsetting.json
nestjs是採用dotenv套件,使用Config Service存取dotenv下的變數。
dotenv是透過讀取.env檔,把key-value pair存到node.js下process.env
假設把資料庫連線的相關訊息存到.dev,檔名可以自行決定,比較多是跟環境有關,比如開發環境就development.env,正式環境就prod.env,未來採用docker或kubernetes可以傳入NODE_ENV來切換,取得該環境下的資料庫連線資訊
把資料庫相關連線資訊放到development.env,這個檔案應該加入gitignore
APP_NAME=IronMan2019_Nestjs
DB_NAME=users
DB_HOST=localhost
DB_PORT=5432
DB_USERNAME=postgres
DB_PW=root
DB_TYPEORM_SYNC=true
DB_TYPEORM_LOG=true
使用Config Service可以更方便在各個nestjs module存取process.env,只要注入Config Service。
新增config.service
export class ConfigService {
private readonly envConfig: {[key:string]:string}
constructor(filePath: string) {
// 讀取.env檔,透過dotenv.parse方法形成key-value pairs
// 存在envConfig變數裡
this.envConfig= dotenv.parse(fs.readFileSync(filePath))
}
// 傳進來key,回傳value
get(key:string){
return this.envConfig[key];
}
// 可以寫方法處理env變數,這樣也比較好除錯
getDbPassword(){
return this.envConfig['DB_PW'];
}
}
為了在其他的module可以重複利用Config Service(import後注入(inject)),建立config.module.ts
@Module({
providers: [
// 這是nestjs另外一種Dependency Injection的方式
{
// 如果nestjs IoC Container要ConfigService的時候
provide: ConfigService,
// 回傳"這個"值
// 剛剛的ConfigService要傳入.env路徑及檔名
useValue: new ConfigService(`${process.env.NODE_ENV || 'development'}.env`)
}
],
// export表示這個Module被import後,ConfigService可以被其他Module Inject
exports: [ConfigService]
})
export class ConfigModule {}
先import config module到app module並修改app controller測試
app.module.ts
@Module({
imports: [
ConfigModule,
SharedModule,
...
})
export class AppModule implements NestModule{
app.controller.ts
@Controller()
export class AppController {
// 注入Config Service
constructor(private configService: ConfigService){
}
@Get()
getAppIndex(){
// 用get並傳入.env底下APP_NAME這個key
return this.configService.get('APP_NAME');
}
}
使用postman測試
再來要refactor TypeORM載入的資料庫連線資訊,主要參考官網Database async部分
有兩種做法,都是在typeorm初始畫資料庫連線之前,要import Config Service取的process.env變數
TypeOrmOptions就是資料庫連線資訊及其他設定如retry、alive time等
照官網作useFactory不成功,在指定type變數部分,typescript complier會出現型別不符合expoConnectionOption,這個問題要另外找答案
錯誤訊息如下
而另外建立TypeOrmConfigService來提供forRootAsync所需要的TypeOrmOptions可以測試OK
建立TypeOrmConfigService
@Injectable()
export class TypeOrmConfigService
// 需要實作TypeOrmOptionsFactory
implements TypeOrmOptionsFactory {
// 注入config service取得env變數
constructor(private readonly configService: ConfigService) {}
// 就是回傳TypeOrmOptions物件
createTypeOrmOptions(): TypeOrmModuleOptions{
return {
type: 'postgres',//configService.get('DB_TYPE') as DatabaseType,
host: this.configService.get('DB_HOST'),
port: Number(this.configService.get('DB_PORT')),
username: this.configService.get('DB_USERNAME'),
password: this.configService.getDbPassword(),
database: this.configService.get('DB_NAME'),
synchronize: this.configService.get('DB_TYPEORM_SYNC') === 'true',
logging: this.configService.get('DB_TYPEORM_LOG') === 'true',
...
};
}
}
修改在shared module typeorm設定
@Module({
imports: [
// 涉及非同步載入Connection Option的時候,改用forRootAsync
TypeOrmModule.forRootAsync({
// 會利用ConfigService,所以要import
imports: [ConfigModule],
inject: [
// 宣告哪個provider或是service需要被注入
ConfigService,
],
// 指定用TypeOrmConfigService,作為載入TypeOrmOptions
// Options就是資料庫連線資訊等
useClass: TypeOrmConfigService,
}),
],
...
}
export class SharedModule {}
使用postman測試